package Resizers;

import org.eclipse.swt.graphics.*;

import Drawers.Drawer;
import Logs.Logger;
import Utils.*;
import TimerLogs.*;

public class BLResizer implements Resizer
{
	private volatile boolean[] _finishedLines;
	private Logger _log;
	
	public void SetLogger(Logger log)
	{
		_log = log;
	}
	
	public Point CanResize(Point requestedSize)
	{
		return requestedSize;
	}
	
	//No intelization needed for NN.
	public void Init(ImageData originalImage, Point maxSize) {}
	
	private class Transformation
	{
		public int SrcIter, TargetIter;
		private int _targetY;
		private int _srcY;
		
		private Point _trgSize , _srcSize;
		private int _bytesPerSrcLine, _bytesPerTargetLine;
		
		public double yFraction, xFraction;
		private double _xFractionStep, _yFractionStep;
		
		private int _xError, _xErrorStep, _xFlooredStep;
		private int _yError, _yErrorStep, _yFlooredStep;
		
		public Transformation(Point srcSize, int bytesPerSrcLine, int bytesPerTargetLine, Point trgSize)
		{
			_srcSize = new Point(srcSize.x, srcSize.y);
			_trgSize = new Point(trgSize.x, trgSize.y);
			_bytesPerSrcLine = bytesPerSrcLine;
			_bytesPerTargetLine = bytesPerTargetLine;
			
			_targetY = 0;
			_xError = 0;
			_yError = 0;
			SrcIter = 0;
			TargetIter = 0;
			_srcY = 0;
			xFraction = 0;
			yFraction = 0;
		
			_xErrorStep = _srcSize.x % _trgSize.x;
			_yErrorStep = _srcSize.y % _trgSize.y;
			

			final double xStep = (double)_srcSize.x / (double)_trgSize.x;
			_xFlooredStep = (int)Math.floor(xStep);
			_xFractionStep = xStep - _xFlooredStep;
			_xFlooredStep *= 3;
			
			final double yStep = (double)_srcSize.y / (double)_trgSize.y;
			_yFlooredStep = (int)Math.floor(yStep);
			_yFractionStep = yStep - _yFlooredStep;
		}
		
		public void moveX()
		{
			TargetIter += 3;
			
			SrcIter += _xFlooredStep;
			if ((_xError += _xErrorStep) >= _trgSize.x)
			{
				SrcIter += 3;
				_xError -= _trgSize.x;
			}
			
			if ((xFraction += _xFractionStep) >= 1)
				xFraction--;
		}
		
		public void moveY()
		{
			_targetY++;
			TargetIter = _bytesPerTargetLine * _targetY;

			_srcY += _yFlooredStep;
			if ((_yError += _yErrorStep) >= _trgSize.y)
			{
				_srcY++;
				_yError -= _trgSize.y;
			}
			SrcIter = _srcY * _bytesPerSrcLine;
			
			_xError = 0;
			xFraction = 0;
			
			if ((yFraction += _yFractionStep) >= 1)
				yFraction--;
		}
		
		public void moveY(int dest)
		{
			_targetY = dest;
			TargetIter = _bytesPerTargetLine * _targetY;

			_srcY = (int)Math.floor(((double)_srcSize.y / (double)_trgSize.y) * (double)dest);
			_yError = _yErrorStep * dest % _trgSize.y;
			SrcIter = _srcY * _bytesPerSrcLine;
			yFraction = _yFractionStep * dest;
			while (yFraction >= 1)
				yFraction--;
			
			_xError = 0;
			xFraction = 0;
		}
		
		public byte GetColor(byte data[], int offset)
		{
			return (byte)(
					(1 - xFraction) * (
						(1 - yFraction) * (data[SrcIter + offset] & 0xFF) + 
						yFraction * (data[SrcIter + _bytesPerSrcLine + offset] & 0xFF)) + 
					xFraction * (
						(1 - yFraction) * (data[SrcIter + 3 + offset] & 0xFF) + 
						yFraction * (data[SrcIter + _bytesPerSrcLine + 3 + offset] & 0xFF)));
		}
	}
	
	public void Resize(ImageData originalImage, Drawer drawer, Point newSize)
	{	
		if (_log == null)
			_log = new DummyLogger();
		
		_log.Start();
		
		_finishedLines = new boolean[newSize.y];
		for (int i = 0; i < newSize.y ; i++)
			_finishedLines[i] = false;
	
		_log.Log("Aquiring memory");
		drawer.Start(newSize, Functions.ClearPalette());
		_log.Log("Aquired memory, starting to interpelate (BiLinear Interpelation)");
		
		MyThread[] threads = new MyThread[2];
		threads[0] = new MyThread(0, originalImage, drawer, newSize);
		threads[1] = new MyThread(newSize.y / 2, originalImage, drawer, newSize);
		
		threads[0].start();
		threads[1].start();
		
		try
		{
		threads[0].join();
		threads[1].join();
		}
		catch (InterruptedException i)
		{
			System.out.format("Cought exception when trying to join threads. Message:%s", i.getMessage());
		}
		
		_log.Log("Finished interpelating image. Starting to interpelate image.");
		
		drawer.Finish();
		
		_log.Log("Finished blitting image");
		_log.Log(String.format(
				"BiLinearized from (%d, %d) to (%d, %d)."
				, originalImage.width, originalImage.height, newSize.x, newSize.y));
		_log.Stop();
	}
	
	public void Interpelate(int startLine, ImageData originalImage, Drawer drawer, Point newSize)
	{
		Transformation doubleIter = new Transformation(
				new Point(originalImage.width - 1, originalImage.height - 1), 
				originalImage.bytesPerLine, drawer.BytesPerLine(),  newSize);
		
		if (startLine != 0)
			doubleIter.moveY(startLine);
		
		for (int y = startLine, count = 0; (count < newSize.y) && !drawer.ToStop(); y = (y + 1) % newSize.y, count++)
		{
			if (!_finishedLines[y])
			{
				_finishedLines[y] = true;
				
				for (int x = 0; x < newSize.x; x++)
				{	
					drawer.PutPixel(doubleIter.TargetIter, 
							doubleIter.GetColor(originalImage.data, 0), 
							doubleIter.GetColor(originalImage.data, 1), 
							doubleIter.GetColor(originalImage.data, 2));
					
					doubleIter.moveX();
				}
			}
			if (y == newSize.y - 1)
				doubleIter.moveY(0);
			else	
				doubleIter.moveY();
		}
	}
	
	private class MyThread extends Thread
	{
		private Point _newSize;
		private int _startLine;
		private ImageData _originalImage;
		private Drawer _drawer; 
		
	    public MyThread(int startLine, ImageData originalImage, Drawer drawer, Point newSize)
	    {
	    	_originalImage = originalImage;
	    	_startLine = startLine;
	    	_drawer = drawer;
	    	_newSize = new Point(newSize.x, newSize.y);
	    }

	    public void run()
	    {
	    	Interpelate(_startLine, _originalImage, _drawer, _newSize);
	    }
	}
}